import { IDisposable } from '../interface';

// eslint-disable-next-line @typescript-eslint/ban-types
export function once<T extends Function>(this: unknown, fn: T): T {
  // eslint-disable-next-line @typescript-eslint/no-this-alias
  const _this = this;
  let didCall = false;
  let result: unknown;

  return function () {
    if (didCall) {
      return result;
    }

    didCall = true;
    result = fn.apply(_this, arguments);

    return result;
  } as unknown as T;
}

export function toDisposable(fn: () => void): IDisposable {
  const self = {
    dispose: once(() => {
      fn();
    }),
  };
  return self;
}

/**
 * 函数防抖---“立即执行版本” 和 “非立即执行版本” 的组合版本
 *
 * @author chitanda
 * @date 2022-06-29 19:06:15
 * @export
 * @param {((...args: any[]) => void | Promise<void>)} func 处理函数
 * @param {number} wait 延迟执行时间（毫秒）
 * @param {boolean} [immediate] 是否立即执行
 * @return {*}  {(...args: any[]) => void}
 */
export function debounce(
  func: (...args: any[]) => void | Promise<void>,
  wait: number,
  immediate?: boolean,
): (...args: any[]) => void {
  let timer;

  return function () {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const context = this;
    const args = arguments;

    if (timer) {
      clearTimeout(timer);
    }
    if (immediate) {
      const callNow = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, wait);
      if (callNow) {
        func.apply(context, args);
      }
    } else {
      timer = setTimeout(function () {
        func.apply(context, args);
      }, wait);
    }
  };
}

/**
 * 节流函数
 *
 * @author chitanda
 * @date 2022-06-29 20:06:38
 * @export
 * @param {((...args: any[]) => void | Promise<void>)} fn
 * @param {number} wait
 * @return {*}  {(...args: any[]) => void}
 */
export function throttle(fn: (...args: any[]) => void | Promise<void>, wait: number): (...args: any[]) => void {
  let timer = null;
  return function () {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const context = this;
    const args = arguments;
    if (!timer) {
      timer = setTimeout(function () {
        fn.apply(context, args);
        timer = null;
      }, wait);
    }
  };
}
